package Cache;

import java.util.PriorityQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 每次添加新结点时，判断Map中是否已有，如果有，移动到队头；没有的话就新建一个结点放入。
 * 对于带过期时间的功能，使用PriorityQueue将过期时间最小的Node放在队首，到时间删除结点。
 *
 * */
class TNode implements Comparable<TNode>{
    private String key;
    private Object value;
    private long expireTime;  //过期时间

    public TNode(String key, Object value, long expireTime) {
        this.key = key;
        this.value = value;
        this.expireTime = expireTime;
    }

    //按照过期时间排序 升序
    @Override
    public int compareTo(TNode o) {
        long r=this.expireTime-o.expireTime;
        if(r>0) return 1;
        if(r<0) return -1;
        return 0;
    }

    public long getExpireTime() {
        return expireTime;
    }

    public String getKey() {
        return key;
    }

    public Object getValue() {
        return value;
    }
}


public class LRUWithTime  {
    //用于设置清除过期数据的线程池
    private static ScheduledThreadPoolExecutor swapExpiredPool =
        new ScheduledThreadPoolExecutor(10);

    //用户存储数据，为了保证线程安全，使用ConcurrentHashMap
    private ConcurrentHashMap<String, TNode> cache=new ConcurrentHashMap<>(1024);
    //保存最新的过期数据，过期时间最小的数据排在队列前
    private PriorityQueue<TNode> expireQueue=new PriorityQueue<>(1024);

    private int capacity;

    //构造方法：只要有缓存了，过期清除线程就开始工作
    public LRUWithTime(int capacity){
        this.capacity = capacity;
        swapExpiredPool.scheduleWithFixedDelay(new ExpiredNode(),3,3, TimeUnit.SECONDS);
    }

    class ExpiredNode implements Runnable {
        @Override
        public void run() {
            //从过期队列弹出队首元素，如果不存在，或者不过期就返回
            while (true) {
                TNode TNode = expireQueue.peek();//获得队首
                //获取当前时间,不过期直接返回，
                long now = System.currentTimeMillis();
                if (TNode == null || TNode.getExpireTime() > now) {
                    return;
                }
                capacity--;  //减小容量
                cache.remove(TNode.getKey());
                expireQueue.poll();//弹出队首
            }
        }
    }
    /**
     * put元素时，需要更新超时时间
     * */
    public Object put(String key, Object value, long ttl){
        //获取过期时间点,过期时间+当前时间=要过期的时间
        long expireTime = System.currentTimeMillis()+ttl;
        //新建一个结点
        TNode newTNode = new TNode(key,value,expireTime);
        //cache中有的话就覆盖，没有就添加新的，过期时间队列也要添加
        TNode old = cache.put(key, newTNode);
        expireQueue.add(newTNode);

        capacity++;
        //如果该key存在数据，还要从过期时间队列删除
        if(old!=null){
            expireQueue.remove(old);
            return old.getValue();
        }
        return null;
    }

    public Object get(String key){
        //从cache直接获取
        TNode n = cache.get(key);
        if(n==null){
            return null;
        }else{
            return n.getValue();
        }
    }

}

class TestLRUWithTime{
    public static void main(String[] args) throws InterruptedException {
        LRUWithTime lruWithTime = new LRUWithTime(3);
        lruWithTime.put("o1",1,3);
        lruWithTime.put("o2",2,3);
        lruWithTime.put("o3",3,3);
        lruWithTime.put("o4",4,3);
        lruWithTime.put("o1",4,3);

        Thread.sleep(1000);

        System.out.println(lruWithTime.get("o2"));
        System.out.println();
    }
}
